package kr.ac.ssu.ailab.Model.Data.Agent;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Random;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EtchedBorder;

import kr.ac.ssu.ailab.Control.CellControl;
import kr.ac.ssu.ailab.Control.ClusterControl;
import kr.ac.ssu.ailab.Control.GraphControl;
import kr.ac.ssu.ailab.Control.MapControl;
import kr.ac.ssu.ailab.Control.NewHeuristicPathFinding;
import kr.ac.ssu.ailab.Model.Data.Cell;
import kr.ac.ssu.ailab.View.SeqTest;

/**
 * @author neil
 * @class AgentGroup
 * @brief agent 들의 집합인 Group. 각 agent 들의 instance 를 포함하고 있으며, 전달된 path에 따른 집단이동,
 *        상황판단 및 level 에 따른 agent 들의 행동방향을 판단하고 명령을 내리게 된다. Agent의 개체수가 1이라도
 *        AgentGroup을 통한 Management만을 허용한다.
 *        
 *        
 *        
 *        
 *        @TODO
 *       Separate each agent's distinguished path according to its level.
 *       1. The domain which is holding module of path-finding, should be agent manager.
 *       2. 
 * */
public class AgentGroup
{
	/**/
	public JFrame frame;
	public JPanel jPanel;

	// passed path from path-finding algorithm
	private Vector<Cell> groupPath;

	private HashMap<String, Agent> agentList;

	public static Enumeration<String> AgentType;
	private Vector<String> tempList = new Vector<String>();
	
	private static String[] typeName;
	
	private static Vector<String> idList;

	// Maximum Number of agent
	public static final int DEF_DIST_BET_AGENTS = 20;
	// count current Number of Agent It'd able to be increased or decreased.
	public static int MAX_NUM_OF_AGENT;
	private static int currentNumOfAgent = 0;
	private static int distBetAgents;

	private static Point currentLocation;
	private Cell finishCell;
	private Cell beforeFinishCell;

	/**
	 * @brief Constructor of Class Agent Group
	 */
	public AgentGroup(Point location, String[] agents)
	{
		this.typeName = agents;
		
		for (int i = 0; i < typeName.length; i++)
		{
			tempList.add(typeName[i]);
		}
		AgentType = tempList.elements();
		
		this.MAX_NUM_OF_AGENT = agents.length;
		
		
		this.agentList = new HashMap<String, Agent>();
		this.idList = new Vector<String>();
		this.currentLocation = location;
		this.distBetAgents = DEF_DIST_BET_AGENTS;
		this.groupPath = new Vector<Cell>();

	}

	public static Point getCurrentPosion()
	{
		return currentLocation;
	}
	public static void setCurrentPositon(Point path)
	{
		currentLocation = path;
	}

	public static void setDistBetAgents(int distBetAgents)
	{
		AgentGroup.distBetAgents = distBetAgents;
	}

	public static int getDistBetAgents()
	{
		return distBetAgents;
	}

	/**
	 * @brief group이 움직일 전체적인 path를 할당한다.
	 * @param ArrayList
	 *            <Point> path
	 * */
	public void setGroupPath(Vector<Cell> path)
	{
		this.groupPath = new Vector<Cell>();
		this.groupPath = path;
		System.err.println("Group Path List :" + groupPath + " "+ path.size());
	}


	/**
	 * @brief group이 움직일 부분적인 (실시간적 변화를 위해) path를 append 한다.
	 * @param Point
	 *            path
	 * */
	public void setGroupPathStep(Cell path)
	{
		this.groupPath.add(path);
	}

	public void setGroupPathAt(int step, Cell point)
	{
		this.groupPath.set(step, point);
	}

	/**
	 * @brief 새롭게 생성되었거나, 기존에 있는 Agent를 현재의 group에 포함시킨다.
	 * @param Agent
	 *            agent
	 * @return null
	 * */
	public void addAgentIntoGroup(Agent agent)
	{
		this.agentList.put(agent.getID(), agent);
		this.jPanel.add(agent);


	}

	/**
	 * @brief 주어진 ID에 해당하는 Agent를 반환한다.
	 * @param String
	 *            ID
	 * @return Agent
	 */
	public Agent getAgentByID(String ID)
	{

		return this.agentList.get(ID);
	}

	public HashMap<String, Agent> getAgentList()
	{
		return this.agentList;
	}

	/**
	 * @brief 현재의 AgnetGroup에 포함된 agent를 List에서 제거한다. 제거가 일어나는 상황은 Agent의
	 *        status가 'Dead' 이거나 타 group으로의 이동이다. 단독적인 Agent의 행동을 허용되지 않는다.
	 * @param Agent
	 *            agent
	 * @return boolean
	 * */
	public boolean removeAgentFromList(Agent agent)
	{
		if (agentList.containsValue(agent))
		{
			this.agentList.remove(agent);
			this.currentNumOfAgent--;
			if (this.idList.contains(agent.getID()))
				this.idList.remove(agent.getID());

			return true;
		}
		else
		{
			System.err.println("The Agent is already removed of doesn't exist in the Group");
			return false;
		}
	}

	/**
	 * @brief target ID를 가진 agent에게 MSG를 보낸다. target Agent는 received 된 MSG에 따른
	 *        행동 양식을 individual decision 따른 reaction을 취한다.
	 * @param MSG
	 *            msg, String agentID
	 * @return null
	 */

	public void sendMsg(MSG msg, String agentID)
	{
		this.agentList.get(agentID).receiveMSG(msg);
		System.err.println("Manager sends MSG : " + msg.getOrder() + " to " + agentID);
	}

	/**
	 * @brief move all agents of group
	 * @param null
	 */
	public boolean moveGroup()
	{
		if (this.groupPath.equals(null))
			return false;

		else
		{
			MSG msg = new MSG(MSG.MOVE, this.groupPath);
			for (int i = 0; i < agentList.size(); i++)
			{
				agentList.get(idList.get(i)).receiveMSG(msg);
			}
			return true;
		}

	}

	public String createID()
	{
		String token = "agent" + currentNumOfAgent;
		this.currentNumOfAgent++;
		this.idList.add(token);
		return token;
	}

	//
	// public static void main(String[] args)
	// {
	// AgentGroup agentGroup = new AgentGroup(new Point(0, 0));
	//
	// /* TEST */
	// agentGroup.frame = new JFrame("test");
	// agentGroup.frame.setPreferredSize(new Dimension(500, 500));
	// agentGroup.frame.setSize(new Dimension(500, 500));
	// agentGroup.frame.setBounds(300, 300, 500, 500);
	// agentGroup.frame.setLayout(null);
	// agentGroup.frame.setDefaultCloseOperation(agentGroup.frame.EXIT_ON_CLOSE);
	//
	// agentGroup.jPanel = new JPanel();
	// agentGroup.jPanel.setBounds(1, 1, 400, 400);
	// agentGroup.jPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
	// agentGroup.jPanel.setBackground(Color.white);
	// agentGroup.jPanel.setOpaque(true);
	// agentGroup.jPanel.setLayout(null);
	// //
	//
	// Random random = new Random(System.currentTimeMillis());
	// for (int i = 0; i < MAX_NUM_OF_AGENT; i++)
	// {
	// agentGroup.addAgentIntoGroup(new Agent(agentGroup.createID(),
	// AgentType.nextElement()
	// .toString(), new Point(random.nextInt() % 100, random.nextInt() % 100)));
	//
	// System.out.println(agentGroup.getAgentByID("agent" + i).getID() + ":"
	// + agentGroup.getAgentByID("agent" + i).getAgentType());
	// }
	//
	// ArrayList<Point> list = new ArrayList<Point>();
	// for (int i = 0; i < 10; i++)
	// {
	// list.add(new Point(i, i + 10));
	// }
	//
	// //agentGroup.setGroupPath(list);
	//
	// for (int i = 0; i < agentGroup.agentList.size(); i++)
	// {
	// agentGroup.getAgentByID("agent" + i).getAgentThread().start();
	// // agentGroup.getAgentByID("agent" +
	// // i).setAgentPath(AgentGroup.groupPath);
	// }
	//
	// // agentGroup.getAgentAt("agent" + 3).setDead();
	// agentGroup.frame.add(agentGroup.jPanel);
	// agentGroup.frame.show();
	// agentGroup.frame.setSize(new Dimension(501, 501));
	//
	// agentGroup.moveGroup();
	// while(agentGroup.getAgentByID("agent0").getAction() == MSG.WAIT)
	// agentGroup.sendMsg(new MSG(MSG.MOVE, list), "agent0");
	//
	// }
	public void findObstacle(Point now, Point enemy)
	{

		int area = 5;
		Vector<Point> list = MapControl.getObstacleList();
		double small = Double.MAX_VALUE;
		Point movePoint = null;
		
		Point point = null;
	
		if (list.isEmpty())
		{
			System.err.println("list is Empty");
			return;
		}
		//포인트 부분에서 
		for (int i = 0; i < list.size(); i++)
		{
			Point2D p = new Point2D.Double(list.get(i).getX(), list.get(i).getY());
			if (p.distance(now.getX(), now.getY()) < small)
			{
				small = p.distance(now.getX(), now.getY());
				movePoint = new Point((int)p.getX(), (int)p.getY());
			}
		}
		if (movePoint == null){

			System.err.println("Move Point is Empty");
			return;
		}
		beforeFinishCell = SeqTest.mapControl.getFinishCell();

		
		if ((now.x > enemy.x) && (now.y > enemy.y))
		{
			for (int i = 1; i <area; i++)
				for (int j = 1; j < area; j++)
				{
	
					if (movePoint.x + i>= SeqTest.mapControl.getMapNumX()|| movePoint.y + j >= SeqTest.mapControl.getMapNumY())
						continue;

					Cell cell = SeqTest.mapControl.getCellArray()[movePoint.x + i][movePoint.y + j];
					if (!cell.isObstacle())
					{
						System.err.println("이동할 셀 : "+cell.getPosition());
						SeqTest.mapControl.setFinishCell(cell);
						return;
					}
				}
		}
		else if ((now.x < enemy.x) && (now.y < enemy.y))
		{
			for (int i = 1; i <area; i++)
				for (int j = 1; j < area; j++)
				{
					if (movePoint.x - i < 0 || movePoint.y - j < 0)
						continue;
					point = new Point(movePoint.x - i,movePoint.y - i);

					Cell cell = SeqTest.mapControl.getCellArray()[movePoint.x - i][movePoint.y - j];
					if (!cell.isObstacle())
					{
						
						System.err.println("이동할 셀 : "+cell.getPosition()+" I : "+i + " J : "+ j);
						SeqTest.mapControl.setFinishCell(cell);
						return;
					}
				}
		}
		else if ((now.x < enemy.x) && (now.y > enemy.y))
		{
			for (int i = 1; i <area; i++)
				for (int j = 1; j < area; j++)
				{
					if (movePoint.x - i < 0)
						continue;
					if (movePoint.y + j >= SeqTest.mapControl.getMapNumY())
						continue;
					Cell cell = SeqTest.mapControl.getCellArray()[movePoint.x - i][movePoint.y + j];
					if (!cell.isObstacle())
					{
						System.err.println("이동할 셀 : "+cell.getPosition());
						SeqTest.mapControl.setFinishCell(cell);
						return;
					}
				}
		}

		else if ((now.x > enemy.x) && (now.y < enemy.y))
		{
			for (int i = 1; i <area; i++)
				for (int j = 1; j < area; j++)
				{
					if (movePoint.y - j < 0)
						continue;
					if (movePoint.x + i>= SeqTest.mapControl.getMapNumX())
						continue;
					Cell cell = SeqTest.mapControl.getCellArray()[movePoint.x + i][movePoint.y - j];
					if (!cell.isObstacle())
					{
						System.err.println("이동할 셀 : "+cell.getPosition());
						SeqTest.mapControl.setFinishCell(cell);
						return;
					}
				}
		}
	}

	public void updatePath()
	{

		System.err.println("FINISH");
		NewHeuristicPathFinding nowHueristic = SeqTest.getFinding();
		GraphControl graphControl = GraphControl.getInstance();
		ClusterControl CCInstance = ClusterControl.getInstance();

		Cell start = nowHueristic.getFirst();
		start.setType(CellControl.START_CELL);

		NewHeuristicPathFinding hueristic = new NewHeuristicPathFinding();

		System.err.println("Start : " + start.getPosition());

		int num = SeqTest.mapControl.getMapNumX() / CCInstance.getNumX();

		SeqTest.mapControl.setStartCell(start);

		CCInstance.setStartCluster(start.getPosition().x / num, start.getPosition().y / num);

		Point p = SeqTest.mapControl.getFinishCell().getPosition();
		CCInstance.setFinishCluster(p.x / num, p.y / num);

		graphControl.clearGraphParent();

		for (int i = 0; i < graphControl.getGoalList().size(); i++)
			graphControl.getGoalList().get(i).setType(CellControl.STAND_CELL);
		graphControl.setNuLLGoalList();
		graphControl.connectSFGraph();
		graphControl.reFindGraph(start);
		NewHeuristicPathFinding.setGoalList(graphControl.getGoalList());
		NewHeuristicPathFinding.change = false;
		hueristic.startSearch();
	}

}
